0%

Scala Note

开一个新坑,Scala这门语言在优化上有很大的操作余地,需要相当的熟练度。本文仅做基础笔记的整理。

Scala

Scala 介绍

Scala 是 Scalable Language 的简写,是一门多范式的编程语言

联邦理工学院洛桑(EPFL)的Martin Odersky于2001年基于Funnel的工作开始设计Scala。

Funnel是把函数式编程思想和Petri网相结合的一种编程语言。

Odersky先前的工作是Generic Java和javac(Sun Java编译器)。Java平台的Scala于2003年底/2004年初发布。.NET平台的Scala发布于2004年6月。该语言第二个版本,v2.0,发布于2006年3月。

截至2009年9月,最新版本是版本2.7.6 。Scala 2.8预计的特性包括重写的Scala类库(Scala collections library)、方法的命名参数和默认参数、包对象(package object),以及Continuation。

2009年4月,Twitter宣布他们已经把大部分后端程序从Ruby迁移到Scala,其余部分也打算要迁移。此外, Wattzon已经公开宣称,其整个平台都已经是基于Scala基础设施编写的。


环境部分:

安装:和Java一样也要配置环境变量

配置IDEA:

先安装插件Scala

然后创建Maven项目

因为Maven默认不支持Scala

创建完毕之后

Scala文件夹标记为Source

语法部分

Hello Scala

1
2
3
4
5
object HelloScala {
def main(args: Array[String]): Unit = {
println("hello Scala")
}
}

命令台执行命令:

1
scala -cp C:\Users\61661\Desktop\scala-1.0-SNAPSHOT.jar HelloScala

声明值和变量

Scala声明变量有两种方式:valvar

val定义的值是不可变的,它不是一个常量,是不可变量,或者称之为只读变量。

Tips:

  1. Scala的匿名变量(为了运行程序,系统自动添加的变量)分配val
  2. val定义的变量虽然不能改变其引用的内存地址,但是可以改变其引用的对象的内部的其他属性值。
  3. 为了减少可变性引起的bug,应该尽可能地使用不可变变量。变量类型可以省略,解析器会根据值进行推断。valvar声明变量时都必须初始化。

常用类型

8种常用类型

类型 属性
Boolean true 或者 false
Byte 8位, 有符号
Short 16位, 有符号
Int 32位, 有符号
Long 64位, 有符号
Char 16位, 无符号
Float 32位, 单精度浮点数
Double 64位, 双精度浮点数
String 由Char数组组成

与Java中的数据类型不同,Scala并不区分基本类型和引用类型,所以这些类型都是对象

可以调用相对应的方法,String直接使用的是java.lang.String

由于String实际是一系列Char的不可变的集合,Scala中大部分针对集合的操作,都可以用于String,具体来说,String的这些方法存在于类scala.collection.immutable.StringOps中。

由于String在需要时能隐式转换为StringOps,因此不需要任何额外的转换,String就可以使用这些方法。

每一种数据类型都有对应的Rich*类型,如RichIntRichChar等,为基本类型提供了更多的有用操作。

常用类型结构图

Scala中,所有的值都是类对象,而所有的类,包括值类型,都最终继承自一个统一的根类型Any。统一类型,是Scala的又一大特点。更特别的是,Scala中还定义了几个底层类Bottom Class,比如NullNothing

  1. Null是所有引用类型的子类型,而Nothing是所有类型的子类型。Null类只有一个实例对象,null,类似于Java中的null引用。null可以赋值给任意引用类型,但是不能赋值给值类型。
  2. Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。
  3. Unit类型用来标识过程,也就是没有明确返回值的函数。 由此可见,Unit类似于Java里的voidUnit只有一个实例,(),这个实例也没有实质的意义。

关系图:

算数操作符重载

+ - * / %可以完成和Java中相同的工作,但是有一点区别,他们都是方法。你几乎可以用任何符号来为方法命名。

1 + 2 等同于 1.+(2)

Tips: Scala中没有++、–操作符,需要通过+=、-=来实现同样的效果。

调用函数与方法

在Scala中,一般情况下我们不会刻意的去区分函数方法的区别,但是他们确实是不同的东西。

后面我们再详细探讨。首先我们要学会使用Scala来调用函数与方法。

1.调用函数,求方根

1
2
import scala.math
sqrt(100)

2.调用方法,静态方法(Scala中没有静态方法这个概念,需要通过伴生类对象来实现)

1
BigInt.probablePrime(16, scala.util.Random)

3.调用方法,非静态方法,使用对象调用

1
"HelloWorld".distinct

4.apply与update方法

apply方法是调用时可以省略方法名的方法。用于构造和获取元素:

1
2
3
4
5
"Hello"(4)  等同于  "Hello".apply(4)
Array(1,2,3) 等同于 Array.apply(1,2,3)
如:
println("Hello"(4))
println("Hello".apply(4))

StringOps中你会发现一个 def apply(n: Int): Char方法定义。update方法也是调用时可以省略方法名的方法,用于元素的更新:

1
2
3
4
5
6
arr(4) = 5  等同于  arr.update(4,5)
如:
val arr1 = new Array[Int](5)
arr1(1) = 2
arr1.update(1, 2)
println(arr1.mkString(","))

Option类型

Scala为单个值提供了对象的包装器,表示为那种可能存在也可能不存在的值。他只有两个有效的子类对象,一个是Some,表示某个值,另外一个是None,表示为空,通过Option的使用,避免了使用null、空字符串等方式来表示缺少某个值的做法。

如:

1
2
3
val map1 = Map("Alice" -> 20, "Bob" -> 30)
println(map1.get("Alice"))
println(map1.get("Jone"))

控制结构和函数

if else

Scala中没有三目运算符,因为根本不需要。Scala中if else表达式是有返回值的,如果if或者else返回的类型不一样,就返回Any类型(所有类型的公共超类型)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
object Demo {
def main(args: Array[String]): Unit = {
val a3 = 10
val a4 =
//返回类型一样
if(a3 > 20){
"a3大于20"
}else{
"a3小于20"
}
val a5 =
if(a3 > 20){
"a3大于20"
}
println(a4)
//a3小于20
println(a5)
//()
}
}

如果缺少一个判断,什么都没有返回,但是Scala认为任何表达式都会有值,对于空值,使用Unit类,写做(),叫做无用占位符,相当于Java中的void。

Tips: 行尾的位置不需要分号,只要能够从上下文判断出语句的终止即可。但是如果在单行中写多个语句,则需要分号分割。在Scala中,{}块包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。

while 表达式

Scala提供和Java一样的while和do循环,与If语句不同,While语句本身没有值,即整个While语句的结果是Unit类型的()。

1
2
3
4
5
6
7
8
9
10
11
12
object Demo {
def main(args: Array[String]): Unit = {
var n = 1
val while1 = while(n <= 10){
n += 1
}

println(while1) //()
println(n) //11
//Scala提供和Java一样的while和do循环,与If语句不同,While语句本身没有值,即整个While语句的结果是Unit类型的()。
}
}

while循环的中断

1
2
3
4
5
6
7
8
9
10
11
import scala.util.control.Breaks
val loop = new Breaks
loop.breakable{
while(n <= 20){
n += 1;
if(n == 18){
loop.break()
}
}
}
println(n)

Tips: Scala并没有提供break和continue语句来退出循环,如果需要break,可以通过几种方法来做1、使用Boolean型的控制变量 2、使用嵌套函数,从函数中return 3、使用Breaks对象的break方法。

for表达式

Scala也为for循环这一常见的控制结构提供了非常多的特性,这些for循环特性被称为for推导式(for comprehension)或for表达式(for expression).

for示例1: to左右两边为前闭后闭的访问
1
2
3
4
for(i <- 1 to 3; j <- 1 to 3){
print(i * j + " ")
}
println()
for示例2:until左右两边为前闭后开的访问
1
2
3
4
for(i <- 1 until 3; j <- 1 until 3) {
print(i * j + " ")
}
println()
for示例3:引入保护式(也称条件判断式)该语句只打印1 3。保护式满足为true则进入循环内部,满足为false则跳过,类似于continue
1
2
3
4
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
println()
for示例4:引入变量
1
2
3
4
for(i <- 1 to 3; j = 4 - i) {
print(j + " ")
}
println()
for示例5:将遍历过程中处理的结果返回到一个,使用yield关键字
1
2
val for5 = for(i <- 1 to 10) yield i
println(for5)
for示例6:使用花括号{}代替小括号()
1
2
3
4
5
for{
i <- 1 to 3
j = 4 - i}
print(i * j + " ")
println()

Tips: {}和()对于for表达式来说都可以。for 推导式有一个不成文的约定:当for
推导式仅包含单一表达式时使用原括号,当其包含多个表达式时使用大括号。值得注意的是,使用原括号时,早前版本的Scala 要求表达式之间必须使用分号。

函数

scala定义函数的标准格式为:

1
def 函数名(参数名1: 参数类型1, 参数名2: 参数类型2) : 返回类型 = {函数体}

函数示例1:返回Unit类型的函数:

1
2
3
def shout1(content: String) : Unit = {
println(content)
}

函数示例2:返回Unit类型的函数,但是没有显式指定返回类型。(当然也可以返回非Unit类型的值)

1
2
3
def shout2(content: String) = {
println(content)
}

函数示例3:返回值类型有多种可能,此时也可以省略Unit

1
2
3
4
5
6
def shout3(content: String) = {
if(content.length >= 3)
content + "喵喵喵~"
else
3
}

函数示例4:带有默认值参数的函数,调用该函数时,可以只给无默认值的参数传递值,也可以都传递,新值会覆盖默认值;传递参数时如果不按照定义顺序,则可以通过参数名来指定。

1
2
3
def shout4(content: String, leg: Int = 4) = {
println(content + "," + leg)
}

函数示例5:变长参数(不确定个数参数,类似Java的…)

1
2
3
4
5
6
def sum(args: Int*) = {
var result = 0
for(arg <- args)
result += arg
result
}

递归函数:递归函数在使用时必须有明确的返回值类型

1
2
3
4
5
6
def factorial(n: Int): Int = {
if(n <= 0)
1
else
n * factorial(n - 1)
}

Tips:

1、Scala可以通过=右边的表达式 推断出函数的返回类型。如果函数体需要多个表达式,可以用代码块{}。

2、可以把return 当做 函数版本的break语句。

3、递归函数一定要指定返回类型。

4、变长参数通过* 来指定,所有参数会转化为一个seq序列。

过程

我们将函数的返回类型为Unit的函数称之为过程。

定义过程示例1:
1
2
3
def shout1(content: String) : Unit = {
println(content)
}

定义过程示例2:

1
2
3
def shout1(content: String) = {
println(content)
}

定义过程示例3:

1
2
3
def shout1(content: String) {
println(content)
}

尖叫提示:这只是一个逻辑上的细分,如果因为该概念导致了理解上的混淆,可以暂时直接跳过过程这样的描述。毕竟过程,在某种意义上也是函数。

懒值

当val被声明为lazy时,他的初始化将被推迟,直到我们首次对此取值,适用于初始化开销较大的场景。

lazy示例:通过lazy关键字的使用与否,来观察执行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
object Lazy {

def init(): String = {
println("init方法执行")
"嘿嘿嘿,我来了~"
}

def main(args: Array[String]): Unit = {
lazy val msg = init()
println("lazy方法没有执行")
println(msg)
}
}

异常

当碰到异常情况时,方法抛出一个异常,终止方法本身的执行,异常传递到其调用者,调用者可以处理该异常,也可以升级到它的调用者。运行系统会一直这样升级异常,直到有调用者能处理它。 如果一直没有处理,则终止整个程序。

Scala的异常的工作机制和Java一样,但是Scala没有“checked”异常,你不需要声明说函数或者方法可能会抛出某种异常。受检异常在编译器被检查,java必须声明方法所会抛出的异常类型。

抛出异常:用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。

捕捉异常:在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句。

异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。

finally字句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作。

异常示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
object ExceptionSyllabus {

def divider(x: Int, y: Int): Float= {
if(y == 0) throw new Exception("0作为了除数")
else x / y
}

def main(args: Array[String]): Unit = {
try {
println(divider(10, 3))
} catch {
case ex: Exception => println("捕获了异常:" + ex)
} finally {}
}
}

数据结构

数据结构特点

Scala同时支持可变集合和不可变集合,不可变集合从不可变,可以安全的并发访问。

两个主要的包:

不可变集合:scala.collection.immutable

可变集合: scala.collection.mutable

Scala优先采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。

不可变集合继承层次:

可变集合继承层次:

数组Array

1.定长数组
1
2
3
4
5
6
7
//定义
val arr1 = new Array[Int](10)
//赋值
arr1(1) = 7
或:
//定义
val arr1 = Array(1, 2)
2.变长数组
1
2
3
4
5
6
//定义
val arr2 = ArrayBuffer[Int]()
//追加值
arr2.append(7)
//重新赋值
arr2(0) = 7
3.定长数据与变长数据的装换
1
2
arr1.toBuffer
arr2.toArray
4.多维数据
1
2
3
4
//定义
val arr3 = Array.ofDim[Double](3,4)
//赋值
arr3(1)(1) = 11.11
5.与Java数组的互转
1
2
3
4
5
6
7
8
9
10
11
12
//scala => Java
val arr4 = ArrayBuffer("1", "2", "3")
//Scala to Java
import scala.collection.JavaConversions.bufferAsJavaList
val javaArr = new ProcessBuilder(arr4)
println(javaArr.command())

//Java => scala
import scala.collection.JavaConversions.asScalaBuffer
import scala.collection.mutable.Buffer
val scalaArr: Buffer[String] = javaArr.command()
println(scalaArr)

6.数据的遍历

1
2
3
for(x <- arr1) {
println(x)
}

5.3 元组 Tuple

元组可以理解为一个容器,可以存放各种相同或者不同类型的数据。

创建
1
2
val tuple1 = (1, 2, 3, "heiheihei")
println(tuple1)
访问(注意元素元素访问邮箱划线,并且访问下标从1开始,而不是0)
1
2
val value1 = tuple1._4
println(value1)
元组的遍历

方式1

1
2
3
4
for (elem <- tuple1.productIterator) {
print(elem)
}
println()

方式2

1
2
tuple1.productIterator.foreach(i => println(i))
tuple1.productIterator.foreach(print(_))

列表List

如果List列表为空,则使用Nil来表示

创建List
1
2
val list1 = List(1, 2)
println(list1)
访问List元素
1
2
val value1 = list1(1)
println(value1)
List元素的追加
1
2
3
4
val list2 = list1 :+ 99
println(list2)
val list3 = 100 +: list1
println(list3)

List的创建与追加,符号“::”,注意观察去掉Nil和不去掉Nil的区别

1
2
val list4 = 1 :: 2 :: 3 :: list1 :: Nil
println(list4)

队列Queue

队列数据存取符合先进先出的策略

队列的创建
1
2
3
import scala.collection.mutable
val q1 = new mutable.Queue[Int]
println(q1)
队列元素的追加
1
2
q1+=1
print;n(q1)
队列的追加
1
2
q1 ++= List(2, 3, 4)
println(q1)
按照进入队列的顺序删除元素
1
2
q1.dequeue()
println(q1)
塞入数据
返回队列的第一个元素
返回队列的最后一个元素
返回队列最后一个元素
返回除了第一个以外的元素
返回除了第一个以外的元素

映射

构造不可变映射
构造可变映射
空的映射
对偶元组
取值
更新值
遍历

集 Set

1.Set不可变集合的创建
2.Set可变集合的创建,如果import了可变集合,那么后续继续使用默认也是可变集合
3.可变集合的元素添加
4.可变集合的元素删除
5.遍历
6.Set更多常用操作

集合元素与函数的映射

map
flatmap

化简、折叠、扫描

折叠,化简:将二次元函数引用于集合中的函数。
折叠,化简:fold